#include "Packet.h"


//////////  DECLARATION & INIT //////////////
CPacket::CPacket()
{
	mBuffer = 0;
	mSeek = 0;
	mSize = 0;
}

 CPacket::CPacket(BYTE id)
{
	mBuffer = 0;
	mSeek = 0;
	mSize = 0;
	mPacketType = id;
}

 CPacket::CPacket(SOCKET s)
 {
	
	 mBuffer = 0;
	 mSeek = 0;
	 mSize = 0;

	 /* this will initialise a packet from a socket/stream */
	 char* tempBuffer = 0;

	 if(safeRecv(s,tempBuffer,sizeof(BYTE))) { memcpy(&mPacketType, tempBuffer, sizeof(BYTE)); free(tempBuffer);  tempBuffer = 0; }
	 if(safeRecv(s,tempBuffer,sizeof(BYTE))) { memcpy(&mSubType, tempBuffer, sizeof(BYTE)); free(tempBuffer); tempBuffer = 0; }
	 if(safeRecv(s,tempBuffer,sizeof(unsigned int))) { memcpy(&mTargetProcess, tempBuffer, sizeof(unsigned int)); free(tempBuffer); tempBuffer = 0; }
	 if(safeRecv(s,tempBuffer,sizeof(unsigned int))) { memcpy(&mSize, tempBuffer, sizeof(unsigned int)); free(tempBuffer); tempBuffer = 0; }
	 if(mSize)
	 {
		if(safeRecv(s,tempBuffer,mSize)) { memcpy(&mBuffer, tempBuffer, mSize); free(tempBuffer); tempBuffer = 0; }
	 }

 }

CPacket::~CPacket()
{
	if(mBuffer) {free(mBuffer); mBuffer = 0; mSeek = 0; mSize = 0; }
}

//////////  PACKING ///////////////

void CPacket::packType(BYTE type, unsigned int size)
{
	char* buffer = new char[sizeof(BYTE)+sizeof(unsigned int)];
	memcpy(buffer+1, &size, sizeof(unsigned int));
	buffer[0] = type;
	packLiteral(buffer, sizeof(BYTE)+sizeof(unsigned int));
	delete [] buffer;
}

void CPacket::pack(BYTE value)
{
	packType(PACKET_BYTE, sizeof(BYTE));
	char* buffer = new char[sizeof(BYTE)];
	memcpy(buffer, &value, sizeof(BYTE));
	packLiteral(buffer, sizeof(BYTE));
	delete [] buffer;
}

void CPacket::pack(bool value)
{
	packType(PACKET_BOOL, sizeof(bool));
	char* buffer = new char[sizeof(bool)];
	memcpy(buffer, &value, sizeof(bool));
	packLiteral(buffer, sizeof(bool));
	delete [] buffer;
}

void CPacket::pack(double value)
{
	packType(PACKET_DOUBLE, sizeof(double));
	char* buffer = new char[sizeof(double)];
	memcpy(buffer, &value, sizeof(double));
	packLiteral(buffer, sizeof(double));
	delete [] buffer;
}

void CPacket::pack(float value)
{
	packType(PACKET_FLOAT, sizeof(float));
	char* buffer = new char[sizeof(float)];
	memcpy(buffer, &value, sizeof(float));
	packLiteral(buffer, sizeof(float));
	delete [] buffer;
}

void CPacket::pack(long value)
{
	packType(PACKET_LONG, sizeof(long));
	char* buffer = new char[sizeof(long)];
	memcpy(buffer, &value, sizeof(long));
	packLiteral(buffer, sizeof(long));
	delete [] buffer;
}

void CPacket::pack(unsigned long value)
{
	packType(PACKET_ULONG, sizeof(unsigned long));
	char* buffer = new char[sizeof(unsigned long)];
	memcpy(buffer, &value, sizeof(unsigned long));
	packLiteral(buffer, sizeof(unsigned long));
	delete [] buffer;
}

void CPacket::pack(unsigned long long value)
{
	packType(PACKET_ULLONG, sizeof(unsigned long long));
	char* buffer = new char[sizeof(unsigned long long)];
	memcpy(buffer, &value, sizeof(unsigned long long));
	packLiteral(buffer, sizeof(unsigned long long));
	delete [] buffer;
}

void CPacket::pack(string value)
{
	packType(PACKET_STRING, value.length());
	const char* buffer = value.c_str();
	packLiteral((char*)buffer, value.length());
}

void CPacket::pack(char* value, unsigned int size)
{
	packType(PACKET_BYTEARRAY, size);
	packLiteral(value, size);
}

/////////// UNPACKING //////////////

BYTE	CPacket::unpackByte()
{

	if(!isNextItemA(PACKET_BYTE)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	BYTE retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

bool	CPacket::unpackBoolean()
{

	if(!isNextItemA(PACKET_BOOL)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	bool retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

double	CPacket::unpackDouble()
{

	if(!isNextItemA(PACKET_DOUBLE)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	double retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

float	CPacket::unpackFloat()
{

	if(!isNextItemA(PACKET_FLOAT)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	float retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

long	CPacket::unpackLong()
{

	if(!isNextItemA(PACKET_ULONG)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	unsigned long retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

unsigned long	CPacket::unpackULong()
{

	if(!isNextItemA(PACKET_ULONG)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	unsigned long retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

unsigned long long	CPacket::unpackULLong()
{

	if(!isNextItemA(PACKET_ULLONG)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	unsigned long long retVal = 0;
	memcpy(&retVal, value, size);
	free(value);
	return retVal;

}

string CPacket::unpackString()
{
	if(!isNextItemA(PACKET_STRING)) { free((void*)1); }
	
	BYTE type = 0;
	unsigned int size = 0;
	char* value = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

	char* retVal = 0;
	retVal = (char*)malloc(size);
	memcpy(&retVal, value, size);
	free(value);

	string retStr = string(retVal);
	free(retVal);

	return string(retStr.c_str()); // de-ref the string so the stack cleans up on exit and not later
}

void	CPacket::unpackByteArray(char *value, unsigned int &size)
{
	if(!isNextItemA(PACKET_BYTEARRAY)) { free((void*)1); }
	
	BYTE type = 0;
	unpackItemHeader(type,size);
	value = (char*)malloc(size);
	unpackLiteral(value, size);

}


/////////// TESTING //////////////

BYTE	CPacket::getTypeOfNextItem()
{
	if(!mBuffer || !mSize) { return 0; }
	return (BYTE)mBuffer[0];
}

bool	CPacket::isNextItemA(BYTE type)
{
	BYTE next = getTypeOfNextItem();
	if(next)
	{
		if(next == type)
			return true;
	}
	return false;
}


/////// PROTECTED INTERNAL FUNCTIONS FOR MANAGING THE BUFFER //////////

void CPacket::packLiteral(char* value, unsigned int size)
{
	// Re Allocate Memory
	mBuffer = (char*)realloc((void*)mBuffer, mSeek + size);
	if(mBuffer == NULL){ free((void*)1); }

	// Move Ptr To Previous Seek Position
	char* pBuffer = mBuffer;
	pBuffer+=mSeek;

	// Copy Memory
	memcpy(pBuffer,value,size);
	
	// Move Seek Position
	mSeek += size;
	mSize = mSeek;

}

void CPacket::unpackLiteral(char* value, unsigned int size)
{
	// check for request error
	if(size > mSize) { free((void*)1); };
	if(value != NULL) { free(value); value = 0; }
	
	// get the data out of the buffer
	value = (char*)malloc(size);
	memcpy(value,mBuffer,size);

	// resize the buffer to exclude the unpacked data
	char* pBuffer = (char*)malloc(mSize - size);
	memcpy(pBuffer, mBuffer + size, mSize - size);
	free(mBuffer);
	mBuffer = pBuffer;

	// Move Seeks
	mSize -= size;
}

void CPacket::unpackItemHeader(BYTE &type, unsigned int &size)
{
	char* chType = 0;
	char* chSize = 0;

	unpackLiteral(chType, sizeof(BYTE));
	unpackLiteral(chSize, sizeof(unsigned int));

	memcpy(&type,chType, sizeof(BYTE));
	free(chType); chType = 0;

	memcpy(&size,chSize, sizeof(unsigned int));
	free(chSize); chSize = 0;
}

//////////////// TRANSMISSION FUNCTIONS ///////////////////////

bool CPacket::transmitPacket(SOCKET s)
{
	
	safeSend(s, (const char*)&mPacketType, sizeof(BYTE));
	safeSend(s, (const char*)&mSubType, sizeof(BYTE));
	safeSend(s, (const char*)&mTargetProcess, sizeof(unsigned int));
	safeSend(s, (const char*)&mSize, sizeof(unsigned int));
	safeSend(s, mBuffer, mSize);
	return true;
	
}

bool CPacket::isWriteable(SOCKET sock)
{
	fd_set write;
	int s;					// amount of sockets that need tending to

	// set time out to 0 so its non-blocking
	timeval time;	
	time.tv_sec = time.tv_usec = 0;
	
	// clear socket set so if anything is in it then there is a connection
	FD_ZERO(&write);			// clear socket set
	FD_SET(sock,&write);

	s = select(0,NULL,&write,NULL,&time);

	if( s > 0)		// then connection is trying to be made
	{
		// Stupid I Know But Must Verify It
		if(FD_ISSET(sock,&write))
		{
			return true;
		}	
	}
	
	// Not Writeable
	return false;
}


bool CPacket::isReadable(SOCKET sock)
{
	fd_set read;
	int s;					// amount of sockets that need tending to

	// set time out to 0 so its non-blocking
	timeval time;	
	time.tv_sec = time.tv_usec = 0;
	
	// clear socket set so if anything is in it then there is a connection
	FD_ZERO(&read);			// clear socket set
	FD_SET(sock,&read);

	s = select(0,&read,NULL,NULL,&time);

	if( s > 0)		// then connection is trying to be made
	{
		// Stupid I Know But Must Verify It
		if(FD_ISSET(sock,&read))
		{
			return true;
		}	
	}
	
	// Not Writeable
	return false;
}

int CPacket::safeRecv(SOCKET s, char* &buffer, unsigned int size)
{
	// init the buffer if not already set, user must ensure that buffer is the correct size if set already
	if(!buffer){buffer = (char*)malloc(size); memset(buffer,0,size);

	fd_set input,error;

	// set time out to 0 so its non-blocking
	timeval time;	
	time.tv_sec = time.tv_usec = 0;

	// Zero Sets then fill with client sockets
	FD_ZERO(&input);
	FD_ZERO(&error);
	FD_SET(s,&input);
	FD_SET(s,&error);

	// check the status of the socket
	int socketCount = select(0,&input,NULL,&error,&time);
	
	if(socketCount > 0)
	{
		// was there an error
		if(FD_ISSET(s,&error))
		{
			/* error with socket clear down the thread and get ready to disconnect */
			return 0;
		}
		
		// has data been recieved
		if(FD_ISSET(s,&input))
		{	
			unsigned long waitingData = 0;
			ioctlsocket(s,FIONREAD,&waitingData);

			if(waitingData)
			{
				// there is data waiting, but is there enough?  Has it all arrived?
				if(waitingData >= size) 
				{
						// Constants
						const int timeout = 3 * 1000; // set to 3 seconds

						// Locals
						DWORD start;
						int ret;

						// wait max of timeout for data to come in
						start = timeGetTime();
						while(!isReadable(s))
						{
							if((timeGetTime() - start)>timeout)
							{
								throw("timeout hit, data not available in time");
							}
						}

						ret = recv(s,buffer,size,0);

						if(ret == 0 || ret == SOCKET_ERROR)
						{
							throw("request to read from socket and no data was available");
						}
						
						return ret;
				}
				else
				{
					/* error, not enough data in the socket */
					return 0;
				}
			}
		}
	}
	}
	return 0;
}

bool CPacket::safeSend(SOCKET sock,const char* data,int size)
{
	const int timeout = 2 * 1000;	// 5 second timeout for data to be sent
	int temp;	DWORD start;
	
	// Check if its writeable for timeout period thefore max send time is 2 * timeout
	start = timeGetTime();
	while( !isWriteable(sock) )
	{
		if( (timeGetTime() - start) > timeout){throw("timeout hit");}
	}

	// Attempt To Send Data
	int nBytes = 0;

	nBytes = send(sock,data,size,0);
	
	// Check For Error
	if(nBytes == SOCKET_ERROR){
		return false;
	}

	// Check For Error
	if(nBytes == 0 && size>0){
		return false;
	}

	// Check All Data Sent
	if(nBytes < size)
	{	

		// Move Pointer Forward
		data += nBytes;

		// Attempt To Send Rest But Watch Timeout
		start = timeGetTime();
		
		while( (size-nBytes)>0 )
		{
			// Check Socket Still Writeable
			if(isWriteable(sock))
			{
				// Attemp To Send
				temp = send(sock,data,(size-nBytes),0);

				// Move Forward
				if(temp == SOCKET_ERROR){return false;}
				else
				{
					nBytes += temp; data+=temp;
				}
			}

			// timeout hit ?
			if( (timeGetTime() - start)>timeout ){throw("timeout hit");}
		}
	}

	return true;
}